#ifndef INSPECTOR_V8INSPECTORCLIENTIMPL_H
#define INSPECTOR_V8INSPECTORCLIENTIMPL_H

#include <locale>
#include <codecvt>
#include "v8-inspector.h"
#include "v8.h"
#include "V8InspectorChannelImpl.h"
#include "block_queue.h"


class V8InspectorClientImpl : public v8_inspector::V8InspectorClient {
public:

    v8::Isolate *Isolate;

    v8::Persistent<v8::Context> Context;

    std::unique_ptr<v8_inspector::V8Inspector> V8Inspector;

    int32_t CtxGroupID;


    bool IsAlive;
    volatile bool IsPaused;
    std::unique_ptr<V8InspectorChannelImpl> V8InspectorChannel;

    RunIfWaitingForDebuggerFunc RunIfWaitingForDebuggerFunc;

    int32_t InspectorId;

    BlockingQueue<std::string> *message_queue;


    V8InspectorClientImpl(v8::Local<v8::Context> InContext, int32_t inspectorId) {
        Isolate = InContext->GetIsolate();
        Context.Reset(Isolate, InContext);
        IsAlive = false;
        CtxGroupID = 1;
        const uint8_t CtxNameConst[] = "V8InspectorContext";
        v8_inspector::StringView CtxName(CtxNameConst, sizeof(CtxNameConst) - 1);
        V8Inspector = v8_inspector::V8Inspector::create(Isolate, this);
        V8Inspector->contextCreated(v8_inspector::V8ContextInfo(InContext, CtxGroupID, CtxName));
        V8InspectorChannel = std::make_unique<V8InspectorChannelImpl>(V8Inspector, CtxGroupID, inspectorId);
        InspectorId = inspectorId;
        IsAlive = true;
        IsPaused = false;
        message_queue = new BlockingQueue<std::string>(100);
    }

    virtual ~V8InspectorClientImpl() {
        Close();
    }


    void runMessageLoopOnPause(int ContextGroupId) override {
        if (IsPaused) {
            return;
        }

        IsPaused = true;
        while (IsPaused) {
            auto message = message_queue->dequeue();
            dispatchMessage(message);
        }
    }


    void runIfWaitingForDebugger(int contextGroupId) override {

        if (RunIfWaitingForDebuggerFunc) {
            RunIfWaitingForDebuggerFunc(InspectorId);
        }
    }

    void quitMessageLoopOnPause() override {
        IsPaused = false;
    }


    void Close() {
        if (IsAlive) {
            V8InspectorChannel.reset();
            v8::Isolate::Scope IsolateScope(Isolate);
            v8::HandleScope HandleScope(Isolate);
            V8Inspector->contextDestroyed(Context.Get(Isolate));
            IsAlive = false;
            IsPaused = false;
        }
        delete message_queue;
    }

    void dispatchMessage(std::string Message) {
        v8::Locker locker(Isolate);
        v8::Isolate::Scope isolate_scope(Isolate);
        v8::HandleScope handle_scope(Isolate);
        std::string data(Message);
        V8InspectorChannel->DispatchProtocolMessage(data);
    }

    void OnReceiveMessage(const char *Message) {
        std::string message(Message);
        if (IsPaused) {
            message_queue->enqueue(message);
        } else {
            dispatchMessage(message);
        }
    }
};


bool bindMessageSendFuncToClient(void *clientPtr, WriteMessageFunc in) {
    V8InspectorClientImpl *client = (V8InspectorClientImpl *) (clientPtr);
    client->V8InspectorChannel->WriteMessageFunc = in;
    return false;
}

bool bindRunIfWaitingForDebuggerFuncToClient(void *clientPtr, RunIfWaitingForDebuggerFunc in) {
    V8InspectorClientImpl *client = (V8InspectorClientImpl *) (clientPtr);
    client->RunIfWaitingForDebuggerFunc = in;
    return false;
}

void onReceiveMessage(void *clientPtr, char *Message) {
    V8InspectorClientImpl *client = (V8InspectorClientImpl *) (clientPtr);
    client->OnReceiveMessage(Message);
}

#endif //INSPECTOR_V8INSPECTORCLIENTIMPL_H
